#include <iostream>
#include <fstream>
#include <utility>
#include <functional>

#include "api/video_codecs/builtin_video_decoder_factory.h"
#include "api/video_codecs/builtin_video_encoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/rtc_event_log/rtc_event_log_factory.h"
#include "api/task_queue/default_task_queue_factory.h"
#include "media/engine/webrtc_media_engine.h"
#include "modules/audio_device/include/fake_audio_device.h"


#include "PeerConnectionManager.h"
#include "CapturerFactory.h"

// Names used for a IceCandidate JSON object.
const char kCandidateSdpMidName[] = "sdpMid";
const char kCandidateSdpMlineIndexName[] = "sdpMLineIndex";
const char kCandidateSdpName[] = "candidate";

// Names used for a SessionDescription JSON object.
const char kSessionDescriptionTypeName[] = "type";
const char kSessionDescriptionSdpName[] = "sdp";

// character to remove from url to make webrtc label
bool ignoreInLabel(char c) { 
    return c==' '||c==':'|| c=='.' || c=='/' || c=='&'; 
}

#include <net/if.h>
#include <ifaddrs.h>

#include <dirent.h>
#include <alsa/asoundlib.h>

std::string getDeviceId(const std::string& evt) {
    std::string deviceid;
    std::istringstream f(evt);
    std::string key;
    while (getline(f, key, '=')) {
        std::string value;
        if (getline(f, value)) {
            if ( (key =="PRODUCT") || (key == "PCI_SUBSYS_ID") ) {
                deviceid = value;
                break;
            }
        }
    }
    return deviceid;
}

std::map<std::string,std::string>  getV4l2AlsaMap() {
    std::map<std::string,std::string> videoaudiomap;

    std::map<std::string,std::string> videodevices;
    std::string video4linuxPath("/sys/class/video4linux");
    DIR *dp = opendir(video4linuxPath.c_str());
    if (dp != NULL) {
        struct dirent *entry = NULL;
        while((entry = readdir(dp))) {
            std::string devicename;
            std::string deviceid;
            if (strstr(entry->d_name,"video") == entry->d_name) {
                std::string devicePath(video4linuxPath);
                devicePath.append("/").append(entry->d_name).append("/name");
                std::ifstream ifsn(devicePath.c_str());
                devicename = std::string(std::istreambuf_iterator<char>{ifsn}, {});
                devicename.erase(devicename.find_last_not_of("\n")+1);

                std::string ueventPath(video4linuxPath);
                ueventPath.append("/").append(entry->d_name).append("/device/uevent");
                std::ifstream ifsd(ueventPath.c_str());
                deviceid = std::string(std::istreambuf_iterator<char>{ifsd}, {});
                deviceid.erase(deviceid.find_last_not_of("\n")+1);
            }

            if (!devicename.empty() && !deviceid.empty()) {
                videodevices[devicename] = getDeviceId(deviceid);
            }
        }
        closedir(dp);
    }

    std::map<std::string,std::string> audiodevices;
    int rcard = -1;
    while ( (snd_card_next(&rcard) == 0) && (rcard>=0) ) {
        void **hints = NULL;
        if (snd_device_name_hint(rcard, "pcm", &hints) >= 0) {
            void **str = hints;
            while (*str) {
                std::ostringstream os;
                os << "/sys/class/sound/card" << rcard << "/device/uevent";

                std::ifstream ifs(os.str().c_str());
                std::string deviceid = std::string(std::istreambuf_iterator<char>{ifs}, {});
                deviceid.erase(deviceid.find_last_not_of("\n")+1);
                deviceid = getDeviceId(deviceid);

                if (!deviceid.empty()) {
                    if (audiodevices.find(deviceid) == audiodevices.end()) {
                        std::string audioname = snd_device_name_get_hint(*str, "DESC");
                        std::replace( audioname.begin(), audioname.end(), '\n', '-');
                        audiodevices[deviceid] = audioname;
                    }
                }

                str++;
            }

            snd_device_name_free_hint(hints);
        }
    }

    for (auto & id : videodevices) {
        auto audioDevice = audiodevices.find(id.second);
        if (audioDevice != audiodevices.end()) {
            std::cout <<  id.first << "=>" << audioDevice->second << std::endl;
            videoaudiomap[id.first] = audioDevice->second;
        }
    }
	
    return videoaudiomap;
}

std::string getServerIpFromClientIp(int clientip) {
    std::string serverAddress;
    char host[NI_MAXHOST];
    struct ifaddrs *ifaddr = NULL;
    if (getifaddrs(&ifaddr) == 0) {
        for (struct ifaddrs* ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
            if ( (ifa->ifa_netmask != NULL) && (ifa->ifa_netmask->sa_family == AF_INET) &&
                 (ifa->ifa_addr != NULL) && (ifa->ifa_addr->sa_family == AF_INET) ) {
                struct sockaddr_in* addr = (struct sockaddr_in*)ifa->ifa_addr;
                struct sockaddr_in* mask = (struct sockaddr_in*)ifa->ifa_netmask;
                if ( (addr->sin_addr.s_addr & mask->sin_addr.s_addr) == (clientip & mask->sin_addr.s_addr) ) {
                    if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, sizeof(host), NULL, 0, NI_NUMERICHOST) == 0)
                    {
                        serverAddress = host;
                        break;
                    }
                }
            }
        }
    }
    freeifaddrs(ifaddr);
    return serverAddress;
}

struct IceServer {
	std::string url;
	std::string user;
	std::string pass;
};

IceServer getIceServerFromUrl(const std::string & url, const std::string& clientIp = "") {
    IceServer srv;
    srv.url = url;
    std::size_t pos = url.find_first_of(':');
    if (pos != std::string::npos) {
        std::string protocol = url.substr(0, pos);
        std::string uri = url.substr(pos + 1);		
        std::string credentials;			
		
        std::size_t pos = uri.find('@');
        if (pos != std::string::npos) {
            credentials = uri.substr(0, pos);			
            uri = uri.substr(pos + 1);
        }
		
        if ( (uri.find("0.0.0.0:") == 0) && (clientIp.empty() == false)) {
            // answer with ip that is on same network as client
            std::string clienturl = getServerIpFromClientIp(inet_addr(clientIp.c_str()));
            clienturl += uri.substr(uri.find_first_of(':'));
            uri = clienturl;
        }
        srv.url = protocol + ":" + uri;

        if (!credentials.empty()) {
            pos = credentials.find('|');
            if (pos == std::string::npos) {
                srv.user = credentials;
            } else {
                srv.user = credentials.substr(0, pos);
                srv.pass = credentials.substr(pos + 1);
            }
        }
    }					
    return srv;
}

webrtc::PeerConnectionFactoryDependencies CreatePeerConnectionFactoryDependencies(
    rtc::scoped_refptr<webrtc::AudioDeviceModule> audioDeviceModule, rtc::scoped_refptr<webrtc::AudioDecoderFactory> audioDecoderfactory) {
    webrtc::PeerConnectionFactoryDependencies dependencies;
    dependencies.network_thread = NULL;
    dependencies.worker_thread = rtc::Thread::Current();
    dependencies.signaling_thread = NULL;
    dependencies.call_factory = webrtc::CreateCallFactory();
    dependencies.task_queue_factory = webrtc::CreateDefaultTaskQueueFactory();	
    dependencies.event_log_factory = absl::make_unique<webrtc::RtcEventLogFactory>(dependencies.task_queue_factory.get());	
	
    cricket::MediaEngineDependencies mediaDependencies;	
    mediaDependencies.task_queue_factory = dependencies.task_queue_factory.get();
    mediaDependencies.adm = std::move(audioDeviceModule);
    mediaDependencies.audio_encoder_factory = webrtc::CreateBuiltinAudioEncoderFactory();
    mediaDependencies.audio_decoder_factory = std::move(audioDecoderfactory);
    mediaDependencies.audio_processing = webrtc::AudioProcessingBuilder().Create();
	
    mediaDependencies.video_encoder_factory = webrtc::CreateBuiltinVideoEncoderFactory();
    mediaDependencies.video_decoder_factory = webrtc::CreateBuiltinVideoDecoderFactory();

    dependencies.media_engine = cricket::CreateMediaEngine(std::move(mediaDependencies));
	
    return std::move(dependencies);
}

PeerConnectionManager::PeerConnectionManager( const std::list<std::string> & iceServerList
                                              , const std::map<std::string,std::string> & urlVideoList
                                              , const std::map<std::string,std::string> & urlAudioList
                                              , const webrtc::AudioDeviceModule::AudioLayer audioLayer
                                              , const std::string& publishFilter)
    : m_task_queue_factory(webrtc::CreateDefaultTaskQueueFactory())
#ifdef HAVE_SOUND
    , m_audioDeviceModule(webrtc::AudioDeviceModule::Create(audioLayer, m_task_queue_factory.get()))
#else
    , m_audioDeviceModule(new webrtc::FakeAudioDeviceModule())
#endif
    , m_audioDecoderfactory(webrtc::CreateBuiltinAudioDecoderFactory())	
    , m_peer_connection_factory(webrtc::CreateModularPeerConnectionFactory(
                                    std::move(CreatePeerConnectionFactoryDependencies(m_audioDeviceModule, m_audioDecoderfactory))))
    , m_iceServerList(iceServerList)
    , m_urlVideoList(urlVideoList)
    , m_urlAudioList(urlAudioList)
    , m_publishFilter(publishFilter) {
    // build video audio map
    m_videoaudiomap = getV4l2AlsaMap();
}

PeerConnectionManager::~PeerConnectionManager() {}

const Json::Value PeerConnectionManager::getMediaList() {
    Json::Value value(Json::arrayValue);
    const std::list<std::string> videoCaptureDevice = CapturerFactory::GetVideoCaptureDeviceList(m_publishFilter);
    for (auto videoDevice : videoCaptureDevice) {
        Json::Value media;
        media["video"] = videoDevice;
		
        std::map<std::string,std::string>::iterator it = m_videoaudiomap.find(videoDevice);
        if (it != m_videoaudiomap.end()) {
            media["audio"] = it->second;
        }				
        value.append(media);
    }

    const std::list<std::string> videoList = CapturerFactory::GetVideoSourceList(m_publishFilter);
    for (auto videoSource : videoList) {
        Json::Value media;
        media["video"] = videoSource;
        value.append(media);
    }

    for (auto url : m_urlVideoList) {
        Json::Value media;
        media["video"] = url.first;
        auto audioIt = m_urlAudioList.find(url.first);
        if (audioIt != m_urlAudioList.end()) {
            media["audio"] = audioIt->first;			
        }
        value.append(media);
    }

    return value;
}

const Json::Value PeerConnectionManager::getVideoDeviceList() {
    Json::Value value(Json::arrayValue);
    const std::list<std::string> videoCaptureDevice = CapturerFactory::GetVideoCaptureDeviceList(m_publishFilter);
    for (auto videoDevice : videoCaptureDevice) {
        value.append(videoDevice);
    }
    return value;
}

const Json::Value PeerConnectionManager::getAudioDeviceList() {
    Json::Value value(Json::arrayValue);
    if (std::regex_match("audiocap://",m_publishFilter)) {		
        int16_t num_audioDevices = m_audioDeviceModule->RecordingDevices();
        RTC_LOG(INFO) << "nb audio devices:" << num_audioDevices;
        for (int i = 0; i < num_audioDevices; ++i) {
            char name[webrtc::kAdmMaxDeviceNameSize] = {0};
            char id[webrtc::kAdmMaxGuidSize] = {0};
            if (m_audioDeviceModule->RecordingDeviceName(i, name, id) != -1) {
                RTC_LOG(INFO) << "audio device name:" << name << " id:" << id;
                value.append(name);
            }
        }
    }
    return value;
}

const Json::Value PeerConnectionManager::getIceServers(const std::string& clientIp) {
    Json::Value urls;
    for (auto iceServer : m_iceServerList) {
        Json::Value server;
        Json::Value urlList(Json::arrayValue);
        IceServer srv = getIceServerFromUrl(iceServer, clientIp);
        RTC_LOG(INFO) << "ICE URL:" << srv.url;
        urlList.append(srv.url);
        server["urls"] = urlList;
        if (srv.user.length() > 0) server["username"] = srv.user;
        if (srv.pass.length() > 0) server["credential"] = srv.pass;
        urls.append(server);
    }	
    Json::Value iceServers;
    iceServers["iceServers"] = urls;
    return iceServers;
}

rtc::scoped_refptr<webrtc::PeerConnectionInterface> PeerConnectionManager::getPeerConnection(const std::string& peerid) {
    rtc::scoped_refptr<webrtc::PeerConnectionInterface> peerConnection;
    std::map<std::string, PeerConnectionObserver* >::iterator  it = m_peer_connectionobs_map.find(peerid);
    if (it != m_peer_connectionobs_map.end()) {
        peerConnection = it->second->getPeerConnection();
    }
    return peerConnection;
}

const Json::Value PeerConnectionManager::addIceCandidate(const std::string& peerid, const Json::Value& jmessage) {
    bool result = false;
    std::string sdp_mid;
    int sdp_mlineindex = 0;
    std::string sdp;
    if (  !rtc::GetStringFromJsonObject(jmessage, kCandidateSdpMidName, &sdp_mid)
          || !rtc::GetIntFromJsonObject(jmessage, kCandidateSdpMlineIndexName, &sdp_mlineindex)
          || !rtc::GetStringFromJsonObject(jmessage, kCandidateSdpName, &sdp)) {
        RTC_LOG(WARNING) << "Can't parse received message:" << jmessage;
    } else {
        std::unique_ptr<webrtc::IceCandidateInterface> candidate(webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, sdp, NULL));
        if (!candidate.get()) {
            RTC_LOG(WARNING) << "Can't parse received candidate message.";
        } else {
            std::lock_guard<std::mutex> peerlock(m_peerMapMutex);
            rtc::scoped_refptr<webrtc::PeerConnectionInterface> peerConnection = this->getPeerConnection(peerid);
            if (peerConnection) {
                if (!peerConnection->AddIceCandidate(candidate.get())) {
                    RTC_LOG(WARNING) << "Failed to apply the received candidate";
                } else {
                    result = true;
                }
            }
        }
    }
    Json::Value answer;
    if (result) {
        answer = result;
    }
    return answer;
}

const Json::Value PeerConnectionManager::createOffer(const std::string &peerid, const std::string & videourl,
                                                     const std::string & audiourl, const std::string & options) {
    RTC_LOG(INFO) << __FUNCTION__ << " video:" << videourl << " audio:" << audiourl << " options:" << options ;
    Json::Value offer;
    sendCadateString = "";

    PeerConnectionObserver* peerConnectionObserver = this->CreatePeerConnection(peerid);
    if (!peerConnectionObserver) {
        RTC_LOG(LERROR) << "Failed to initialize PeerConnection";
    } else {
        rtc::scoped_refptr<webrtc::PeerConnectionInterface> peerConnection = peerConnectionObserver->getPeerConnection();
        if (!this->AddStreams(peerConnection, videourl, audiourl, options)) {
            RTC_LOG(WARNING) << "Can't add stream";
        }
        // register peerid
        {
            std::lock_guard<std::mutex> peerlock(m_peerMapMutex);
            m_peer_connectionobs_map.insert(std::pair<std::string, PeerConnectionObserver* >(peerid, peerConnectionObserver));
        }

        // ask to create offer
        webrtc::PeerConnectionInterface::RTCOfferAnswerOptions rtcoptions;
        rtcoptions.offer_to_receive_video = 0;
        rtcoptions.offer_to_receive_audio = 0;		
        std::promise<const webrtc::SessionDescriptionInterface*> promise;
        peerConnection->CreateOffer(CreateSessionDescriptionObserver::Create(peerConnection, promise), rtcoptions);

        // waiting for offer
        std::future<const webrtc::SessionDescriptionInterface*> future = promise.get_future();
        if (future.wait_for(std::chrono::milliseconds(5000)) == std::future_status::ready ) {
            // answer with the created offer
            const webrtc::SessionDescriptionInterface* desc = future.get();
            if (desc) {
                std::string sdp;
                desc->ToString(&sdp);
                offer[kSessionDescriptionTypeName] = desc->type();
                offer[kSessionDescriptionSdpName] = sdp;
            } else {
                RTC_LOG(LERROR) << "Failed to create offer";
            }
        } else {
            RTC_LOG(LERROR) << "Failed to create offer";
        }
    }
    return offer;
}

/*  set answer to a call initiated by createOffer */
void PeerConnectionManager::setAnswer(const std::string &peerid, const Json::Value& jmessage) {
    RTC_LOG(INFO) << jmessage;
    std::string type;
    std::string sdp;
    if (  !rtc::GetStringFromJsonObject(jmessage, kSessionDescriptionTypeName, &type)
          || !rtc::GetStringFromJsonObject(jmessage, kSessionDescriptionSdpName, &sdp)) {
        RTC_LOG(WARNING) << "Can't parse received message.";
    } else {
        webrtc::SessionDescriptionInterface* session_description(webrtc::CreateSessionDescription(type, sdp, NULL));
        if (!session_description) {
            RTC_LOG(WARNING) << "Can't parse received session description message.";
        } else {
            RTC_LOG(LERROR) << "From peerid:" << peerid << " received session description :" << session_description->type();
            std::lock_guard<std::mutex> peerlock(m_peerMapMutex);
            rtc::scoped_refptr<webrtc::PeerConnectionInterface> peerConnection = this->getPeerConnection(peerid);
            if (peerConnection) {
                std::promise<const webrtc::SessionDescriptionInterface*> promise;
                peerConnection->SetRemoteDescription(SetSessionDescriptionObserver::Create(peerConnection, promise), session_description);
            }
        }
    }
}

bool PeerConnectionManager::streamStillUsed(const std::string & streamLabel) {
    bool stillUsed = false;
    for (auto it: m_peer_connectionobs_map) {
        rtc::scoped_refptr<webrtc::PeerConnectionInterface> peerConnection = it.second->getPeerConnection();
        rtc::scoped_refptr<webrtc::StreamCollectionInterface> localstreams (peerConnection->local_streams());
        for (unsigned int i = 0; i < localstreams->count(); i++) {
            if (localstreams->at(i)->id() == streamLabel) {
                stillUsed = true;
                break;
            }
        }
    }
    return stillUsed;
}

const Json::Value PeerConnectionManager::hangUp(const std::string &peerid) {
    bool result = false;
    RTC_LOG(INFO) << __FUNCTION__ << " " << peerid;
    PeerConnectionObserver* pcObserver = NULL;

    {
        std::lock_guard<std::mutex> peerlock(m_peerMapMutex);
        std::map<std::string, PeerConnectionObserver* >::iterator  it = m_peer_connectionobs_map.find(peerid);
        if (it != m_peer_connectionobs_map.end()) {
            pcObserver = it->second;
            RTC_LOG(LS_ERROR) << "Remove PeerConnection peerid:" << peerid;
            m_peer_connectionobs_map.erase(it);
        }

        if (pcObserver) {
            rtc::scoped_refptr<webrtc::PeerConnectionInterface> peerConnection = pcObserver->getPeerConnection();
            rtc::scoped_refptr<webrtc::StreamCollectionInterface> localstreams(peerConnection->local_streams());
            for (unsigned int i = 0; i < localstreams->count(); i++) {
                auto stream = localstreams->at(i);
                std::string streamLabel = stream->id();
                bool stillUsed = this->streamStillUsed(streamLabel);
                if (!stillUsed) {
                    RTC_LOG(LS_ERROR) << "hangUp stream is no more used " << streamLabel;
                    std::lock_guard<std::mutex> mlock(m_streamMapMutex);
                    std::map <std::string, std::pair<rtc::scoped_refptr<webrtc::VideoTrackInterface>,
                                                     rtc::scoped_refptr<webrtc::AudioTrackInterface>> >::iterator it = m_stream_map.find(streamLabel);
                    if (it != m_stream_map.end()) {
                        m_stream_map.erase(it);
                    }
                    RTC_LOG(LS_ERROR) << "hangUp stream closed " << streamLabel;
                }
                RTC_LOG(LS_ERROR) << "here";
                peerConnection->RemoveStream(stream);
                RTC_LOG(LS_ERROR) << "here";
            }
            delete pcObserver;
            result = true;
        }
    }
    Json::Value answer;
    if (result) {
        answer = result;
    }
    RTC_LOG(INFO) << __FUNCTION__ << " " << peerid << " result:" << result;	
    return answer;
}

const Json::Value PeerConnectionManager::getIceCandidateList(const std::string &peerid) {
    Json::Value value;
    std::lock_guard<std::mutex> peerlock(m_peerMapMutex);
    std::map<std::string, PeerConnectionObserver* >::iterator  it = m_peer_connectionobs_map.find(peerid);
    if (it != m_peer_connectionobs_map.end()) {
        PeerConnectionObserver* obs = it->second;
        if (obs) {
            value = obs->getIceCandidateList();
        } else {
            RTC_LOG(LS_ERROR) << "No observer for peer:" << peerid;
        }
    }
    return value;
}

const Json::Value PeerConnectionManager::getPeerConnectionList() {
    Json::Value value(Json::arrayValue);

    std::lock_guard<std::mutex> peerlock(m_peerMapMutex);
    for (auto it : m_peer_connectionobs_map) {
        Json::Value content;
        // get local SDP
        rtc::scoped_refptr<webrtc::PeerConnectionInterface> peerConnection = it.second->getPeerConnection();
        if ( (peerConnection) && (peerConnection->local_description()) ) {
            std::string sdp;
            peerConnection->local_description()->ToString(&sdp);
            content["sdp"] = sdp;

            Json::Value streams;
            rtc::scoped_refptr<webrtc::StreamCollectionInterface> localstreams (peerConnection->local_streams());
            if (localstreams) {
                for (unsigned int i = 0; i<localstreams->count(); i++) {
                    if (localstreams->at(i)) {
                        Json::Value tracks;
                        const webrtc::VideoTrackVector& videoTracks = localstreams->at(i)->GetVideoTracks();
                        for (unsigned int j=0; j<videoTracks.size() ; j++) {
                            auto videoTrack = videoTracks.at(j);
                            Json::Value track;
                            track["kind"] = videoTrack->kind();
                            webrtc::VideoTrackSourceInterface::Stats stats;
                            if (videoTrack->GetSource()) {
                                track["state"] = videoTrack->GetSource()->state();
                                if (videoTrack->GetSource()->GetStats(&stats)) {
                                    track["width"] = stats.input_width;
                                    track["height"] = stats.input_height;
                                }
                            }
                            tracks[videoTrack->id()] = track;
                        }
                        const webrtc::AudioTrackVector& audioTracks = localstreams->at(i)->GetAudioTracks();
                        for (unsigned int j=0; j<audioTracks.size() ; j++) {
                            auto audioTrack = audioTracks.at(j);
                            Json::Value track;
                            track["kind"] = audioTrack->kind();
                            if (audioTrack->GetSource()) {
                                track["state"] = audioTrack->GetSource()->state();
                            }
                            tracks[audioTrack->id()] = track;
                        }
						
                        streams[localstreams->at(i)->id()] = tracks;
                    }
                }
            }
            content["streams"] = streams;
        }
        Json::Value pc;
        pc[it.first] = content;
        value.append(pc);
    }
    return value;
}

const Json::Value PeerConnectionManager::getStreamList() {
    std::lock_guard<std::mutex> mlock(m_streamMapMutex);
    Json::Value value(Json::arrayValue);
    for (auto it : m_stream_map) {
        value.append(it.first);
    }
    return value;
}

bool PeerConnectionManager::InitializePeerConnection() {
    return (m_peer_connection_factory.get() != NULL);
}

PeerConnectionManager::PeerConnectionObserver* PeerConnectionManager::CreatePeerConnection(const std::string& peerid) {
    webrtc::PeerConnectionInterface::RTCConfiguration config;
    config.enable_dtls_srtp = true;
    for (auto iceServer : m_iceServerList) {
        webrtc::PeerConnectionInterface::IceServer server;
        IceServer srv = getIceServerFromUrl(iceServer);
        server.uri = srv.url;
        server.username = srv.user;
        server.password = srv.pass;
        config.servers.push_back(server);
    }

    RTC_LOG(INFO) << __FUNCTION__ << "CreatePeerConnection peerid:" << peerid;
    PeerConnectionObserver* obs = new PeerConnectionObserver(this, peerid, config);
    if (!obs) {
        RTC_LOG(LERROR) << __FUNCTION__ << "CreatePeerConnection failed";
    }
    return obs;
}

rtc::scoped_refptr<webrtc::VideoTrackInterface> PeerConnectionManager::CreateVideoTrack(
    const std::string & videourl, const std::map<std::string,std::string> & opts) {
    RTC_LOG(INFO) << "videourl:" << videourl;

    std::string video = videourl;
    auto videoit = m_urlVideoList.find(video);
    if (videoit != m_urlVideoList.end()) {
        video = videoit->second;
    }

    std::string label = this->sanitizeLabel(videourl + "_video");

    rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track;
    rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> videoSource =
        CapturerFactory::CreateVideoSource(video, opts, m_publishFilter, m_peer_connection_factory);
    if (!videoSource) {
        RTC_LOG(LS_ERROR) << "Cannot create capturer video:" << videourl;
    } else {
        video_track = m_peer_connection_factory->CreateVideoTrack(label, videoSource);
    }

    return video_track;
}

rtc::scoped_refptr<webrtc::AudioTrackInterface> PeerConnectionManager::CreateAudioTrack(
    const std::string & audiourl, const std::map<std::string,std::string> & opts) {
    RTC_LOG(INFO) << "audiourl:" << audiourl;

    std::string audio = audiourl;
    auto audioit = m_urlAudioList.find(audio);
    if (audioit != m_urlAudioList.end()) {
        audio = audioit->second;
    }

    std::map<std::string,std::string>::iterator it = m_videoaudiomap.find(audio);
    if (it != m_videoaudiomap.end()) {
        audio = it->second;
    }

    rtc::scoped_refptr<webrtc::AudioSourceInterface> audioSource;
    if ( (audio.find("rtsp://") == 0) && (std::regex_match("rtsp://",m_publishFilter)) ) {
#ifdef HAVE_LIVE555
        m_audioDeviceModule->Terminate();
        audioSource = RTSPAudioSource::Create(m_audioDecoderfactory, audio, opts);
#endif
    } else if (std::regex_match("audiocap://",m_publishFilter)) {
        m_audioDeviceModule->Init();
        int16_t num_audioDevices = m_audioDeviceModule->RecordingDevices();
        int16_t idx_audioDevice = -1;
        for (int i = 0; i < num_audioDevices; ++i) {
            char name[webrtc::kAdmMaxDeviceNameSize] = {0};
            char id[webrtc::kAdmMaxGuidSize] = {0};
            if (m_audioDeviceModule->RecordingDeviceName(i, name, id) != -1) {
                if (audio == name) {
                    idx_audioDevice = i;
                    break;
                }
            }
        }
        RTC_LOG(LS_ERROR) << "audiourl:" << audiourl << " idx_audioDevice:" << idx_audioDevice;
        if ( (idx_audioDevice >= 0) && (idx_audioDevice < num_audioDevices) ) {
            m_audioDeviceModule->SetRecordingDevice(idx_audioDevice);
            cricket::AudioOptions opt;
            audioSource = m_peer_connection_factory->CreateAudioSource(opt);
        }
    }
	
    rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track;
    if (!audioSource) {
        RTC_LOG(LS_ERROR) << "Cannot create capturer audio:" << audiourl;
    } else {
        std::string label = this->sanitizeLabel(audiourl + "_audio");
        audio_track = m_peer_connection_factory->CreateAudioTrack(label, audioSource);
    }
    return audio_track;
}

const std::string PeerConnectionManager::sanitizeLabel(const std::string &label) {
    std::string out(label);
	
    // conceal labels that contain rtsp URL to prevent sensitive data leaks.
    if (label.find("rtsp:") != std::string::npos) {
        std::hash<std::string> hash_fn;
        size_t hash = hash_fn(out);
        return std::to_string(hash);
    }
	
    out.erase(std::remove_if(out.begin(), out.end(), ignoreInLabel), out.end());
    return out;
}

bool PeerConnectionManager::AddStreams(webrtc::PeerConnectionInterface* peer_connection, const std::string & videourl,
                                       const std::string & audiourl, const std::string & options) {
    bool ret = false;

    // compute stream label removing space because SDP use label
    std::string streamLabel = this->sanitizeLabel(videourl + "|" + audiourl + "|" + options);

    bool existingStream = false;
    {
        std::lock_guard<std::mutex> mlock(m_streamMapMutex);
        existingStream = (m_stream_map.find(streamLabel) != m_stream_map.end());
    }
	
    if (!existingStream) {
        // compute audiourl if not set
        std::string audio(audiourl);
        if (audio.empty()) {
            audio = videourl;
        }

        // convert options string into map
        std::istringstream is(options);
        std::map<std::string,std::string> opts;
        std::string key, value;
        while(std::getline(std::getline(is, key, '='), value,'&')) {
            opts[key] = value;	
        }

        // set bandwidth
        if (opts.find("bitrate") != opts.end()) {
            int bitrate = std::stoi(opts.at("bitrate"));
            webrtc::PeerConnectionInterface::BitrateParameters bitrateParam;
            bitrateParam.min_bitrate_bps = absl::optional<int>(bitrate/2);
            bitrateParam.current_bitrate_bps = absl::optional<int>(bitrate);
            bitrateParam.max_bitrate_bps = absl::optional<int>(bitrate*2);
            peer_connection->SetBitrate(bitrateParam);			
			
            RTC_LOG(WARNING) << "set bitrate:" << bitrate;
        }					
		
        // need to create the stream
        rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track(this->CreateVideoTrack(videourl, opts));
        rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(this->CreateAudioTrack(audio, opts));
        RTC_LOG(INFO) << "Adding Stream to map";
        std::lock_guard<std::mutex> mlock(m_streamMapMutex);
        m_stream_map[streamLabel] = std::make_pair(video_track,audio_track);
    }

    {
        std::lock_guard<std::mutex> mlock(m_streamMapMutex);
        std::map<std::string, std::pair<rtc::scoped_refptr<webrtc::VideoTrackInterface>,
                                        rtc::scoped_refptr<webrtc::AudioTrackInterface>> >::iterator it = m_stream_map.find(streamLabel);
        if (it != m_stream_map.end()) {
            rtc::scoped_refptr<webrtc::MediaStreamInterface> stream = m_peer_connection_factory->CreateLocalMediaStream(streamLabel);
            if (!stream.get()) {
                RTC_LOG(LS_ERROR) << "Cannot create stream";
            } else {
                std::pair < rtc::scoped_refptr<webrtc::VideoTrackInterface>, rtc::scoped_refptr<webrtc::AudioTrackInterface> > pair = it->second;
                rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track(pair.first);
                if ( (video_track) && (!stream->AddTrack(video_track)) ) {
                    RTC_LOG(LS_ERROR) << "Adding VideoTrack to MediaStream failed";
                } 
                rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(pair.second);
                if ( (audio_track) && (!stream->AddTrack(audio_track)) ) {
                    RTC_LOG(LS_ERROR) << "Adding AudioTrack to MediaStream failed";
                }

                if (!peer_connection->AddStream(stream)) {
                    RTC_LOG(LS_ERROR) << "Adding stream to PeerConnection failed";
                } else {
                    RTC_LOG(INFO) << "stream added to PeerConnection";
                    ret = true;
                }
            }
        } else {
            RTC_LOG(LS_ERROR) << "Cannot find stream";
        }
    }
    return ret;
}

void PeerConnectionManager::PeerConnectionObserver::OnIceCandidate(const webrtc::IceCandidateInterface* candidate) {
    RTC_LOG(INFO) << __FUNCTION__ << " " << candidate->sdp_mline_index();

    std::string sdp;
    if (!candidate->ToString(&sdp)) {
        RTC_LOG(LS_ERROR) << "Failed to serialize candidate";
    } else {
        RTC_LOG(INFO) << sdp;

        Json::Value jmessage;
        jmessage[kCandidateSdpMidName] = candidate->sdp_mid();
        jmessage[kCandidateSdpMlineIndexName] = candidate->sdp_mline_index();
        jmessage[kCandidateSdpName] = sdp;
        m_iceCandidateList.append(jmessage);
    }
}


